﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.Text;
using nwrole.lib;
using nwrole.plugin;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;

namespace nwrole.lib2 {
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class PluginManager : IServiceContract {

        [System.ComponentModel.Composition.ImportMany]
        IEnumerable<IPlugin> Plugins { get; set; }

        private List<PluginWrapper> pluginWrappers = new List<PluginWrapper>();
        private int supervisorPort;
        private string serviceName;

        private List<PluginData> PluginDatas = new List<PluginData>();

        private static void WriteLog(string content) {
            DateTime now = DateTime.Now;
            var path = "c:/log/nwrole/PluginService/";
            if (!System.IO.Directory.Exists(path))
                System.IO.Directory.CreateDirectory(path);

            System.IO.File.AppendAllText(path + "log " + now.ToString("yyyy-MM-dd") + ".txt", now.ToString("yyyy-MM-dd hh:mm:ss ") + content + "\r\n");
        }

        public Guid ServiceId { get; private set; }

        public PluginManager(Guid serviceId, string serviceName, int supervisorPort) {
            ServiceId = serviceId;
            this.supervisorPort = supervisorPort;
            this.serviceName = serviceName;
        }

        public Guid GetIdentifier() {
            return ServiceId;
        }

        public Result Start() {
            WriteLog("PluginManager.Start()");
            Configuration.Load();

            WriteLog("PluginManager service name: " + serviceName);

            PluginDatas.Clear();

            var catalog = new AggregateCatalog();
            var container = new CompositionContainer(catalog);
            var batch = new CompositionBatch();
            batch.AddPart(this);
            catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            catalog.Catalogs.Add(new DirectoryCatalog(ServicePath));
            container.Compose(batch);

            WriteLog("PluginManager num plugins: " + Plugins.Count());

            bool configChanged = false;

            LoadConfig();

            foreach (var plugin in Plugins) {
                PluginWrapper pluginWrapper = new PluginWrapper(plugin);
                pluginWrappers.Add(pluginWrapper);
                plugin.LogEvent += OnPluginLog;

                WriteLog("Plugin class: " + plugin.GetType().FullName);

                PluginData pluginData = PluginDatas.SingleOrDefault(p => p.ClassName == plugin.GetType().FullName);

                if (pluginData == null) {
                    pluginData = new PluginData() { ClassName = plugin.GetType().FullName, Enabled = false };
                    PluginDatas.Add(pluginData);
                    configChanged = true;
                }

                if (pluginData.Enabled) {
                    pluginWrapper.Start();
                }
            }

            if (configChanged) {
                SaveConfig();
            }

            return Result.Success;
        }

        public Result Stop() {

            foreach (var pluginWrapper in pluginWrappers) {
                pluginWrapper.Stop();
            }

            return Result.Success;
        }

        private string ServicePath {
            get {
                return Path.Combine(Configuration.ServicesDirectory, serviceName);
            }
        }
        private string ConfigPath {
            get {
                return Path.Combine(ServicePath, "plugin-config.xml");
            }
        }

        private void LoadConfig() {
            if (File.Exists(ConfigPath)) {
                PropertyList plist = new PropertyList(ConfigPath);

                IList<object> plugins = plist.ArrayForKey("Plugins");

                foreach (object obj in plugins)
                    PluginDatas.Add(StorageUtil.PluginDataFromDictionary((IDictionary)obj));
            }
        }

        private void SaveConfig() {
            var plugins = new List<object>();

            foreach (PluginData pluginData in PluginDatas)
                plugins.Add(StorageUtil.PluginDataToDictionary(pluginData));

            PropertyList plist = new PropertyList();
            plist["Plugins"] = plugins;
            plist.Save(ConfigPath);
        }

        /*private void ConnectToSupervisor() {
            EndpointAddress address = new EndpointAddress("net.tcp://localhost:" + supervisorPort);
            NetTcpBinding binding = new NetTcpBinding();
            binding.ReceiveTimeout = new TimeSpan(0, 0, 120);
            binding.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
            binding.MaxReceivedMessageSize = 2147483647;
            ChannelFactory<ISupervisorContract> factory = new ChannelFactory<ISupervisorContract>(binding, address);
            factory.Credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
            manager = factory.CreateChannel();
        }*/

        public void OnPluginLog(object sender, PluginLogEventArgs e) {
            //WriteLog("OnPluginLog isError:" + e.IsError + " msg:" + e.Message);
            if (e.IsError) {
                using (var supervisor = new TcpChannel<ISupervisorContract>("localhost:" + supervisorPort + "/Supervisor")) {
                    WriteLog("SupervisorContract");
                    try {
                        supervisor.Channel.ReportError(e.Message);
                    } catch (Exception ex) {
                        WriteLog("SupervisorContract OnPluginLog port:" + supervisorPort + " exception: " + ex.ToString());
                        EventLogger.LogException("OnPluginLog port:" + supervisorPort + " exception: ", ex);
                    }
                }
            }
        }

        public static string ServiceDirectoryPath(ServiceData serviceData) {
            return Path.Combine(Configuration.ServicesDirectory, serviceData.Name);
        }
    }
}
